package suncertify.gui;

import javax.swing.JFrame;
import javax.swing.JTable;

import suncertify.application.ApplicationMode;
import suncertify.application.ClientApplication;
import suncertify.application.Log;
import suncertify.application.Strings;
import suncertify.contractors.Contractor;
import suncertify.contractors.ContractorsAccess;
import suncertify.contractors.exceptions.CloseContractorsException;
import suncertify.contractors.exceptions.ContractorFindException;
import suncertify.contractors.exceptions.ReadAllContractorsException;
import suncertify.db.Data;
import suncertify.gui.dialogs.BookingDialog;

/**
 * This class represents the <b>controller</b> for {@link ClientApplicationFrame} <b>view</b>, <b>controller</b>
 * represents the controller part of the MVC pattern and <b>view</b> represents the view part. The view sends all its
 * user interactions to the controller, the controller executes the requests it receives from the view and updates the
 * model data accordingly.
 * 
 * <p>
 * This controller class receives three kinds of requests from its view:
 * <ol>
 * <li>Requests that cause the whole view to update, these requests are issued when the user refreshes the view or
 * searches for specific contractor(s). In this case the controller creates a new model and converts the new model into
 * a new view.</li>
 * <li>Requests that cause only part of the view to change like the request to book a contractor for a specific owner.
 * In this case the controller updates the contractor in the database and synchronizes the existing view with the model
 * to reflect the change.</li>
 * <li>Requests that don't affect the view data like terminating the application.</li>
 * </ol>
 * 
 * <p>
 * To handle all three kinds of requests mentioned above the controller uses these references:
 * <ol>
 * <li>The {@link ContractorsAccess} implementor, this reference is used to access the data in the database, it is
 * assigned in {@link #setContractorsAccess(ContractorsAccess)}.</li>
 * <li>The GUI component that represents the view, this is a reference to the {@link JTable} object, it is assigned in
 * {@link #setViewControl(Object)}.</li>
 * <li>{@link ContractorsViewInterface}, this reference is the implementation of the swing {@link JTable} model, it also
 * represents the view for this controller.</li>
 * <li>The {@link ClientApplication}, this reference is used to handle the terminate request from the view.</li>
 * </ol>
 * 
 * <p>
 * The references mentioned above are not provided all in the constructor because they are not available at the same,
 * e.g. the {@link ClientApplication} reference is always available when the application starts, while the
 * {@link ContractorsAccess} reference is only available after the user connects to the data successfully. Check the
 * {@link ClientApplication#startApplication()} for details exact details on how the controller is used in conjunction
 * with its view.
 * 
 */
public class ClientApplicationFrameController implements ClientApplicationFrameControllerInterface {

    /**
     * Reference to the object that represents the view. In this application this object will be of type JTable.
     */
    private Object viewControl;

    /**
     * This member variable represents the model part of the MVC.
     */
    private ContractorsViewInterface contractorsView;

    /**
     * The implementor of the {@link ContractorsAccess}. This object provides the data connection for the controller.
     */
    private ContractorsAccess contractorsAccess;

    /**
     * Reference to the {@link ClientApplication} object.
     */
    private ClientApplication clientApplication;

    /**
     * Constructs the class using the provided parameter which is stored as a class member.
     * 
     * @param clientApplication
     *            See {@link ClientApplication}
     */
    public ClientApplicationFrameController(ClientApplication clientApplication) {
	this.clientApplication = clientApplication;
    }

    /**
     * Assigns the parameter to the corresponding member variable.
     * 
     * @param contractorsTable
     *            Reference to the view GUI component e.g. a {@link JTable}
     * 
     */
    public void setViewControl(Object contractorsTable) {
	this.viewControl = contractorsTable;
    }

    /**
     * Assigns the parameter to the corresponding member variable.
     * 
     * @param contractorsAccess
     *            Provides the data source for the controller.
     */
    public void setContractorsAccess(ContractorsAccess contractorsAccess) {
	this.contractorsAccess = contractorsAccess;
    }

    /**
     * Returns the application mode. Simply calls {@link ClientApplication#getApplicationMode()}.
     * 
     * @return The application mode.
     * 
     */
    public ApplicationMode getApplicationMode() {
	return this.clientApplication.getApplicationMode();
    }

    /**
     * Retrieves all contractors from the database using {@link ContractorsAccess} reference then creates a new
     * contractors view model by calling the {@link ContractorsViewFactory#createContractorsView(Object, Contractor[])}
     * method. The contractors view reference is saved in the class.
     * 
     * @throws ReadAllContractorsException
     *             Thrown if an error occurs during the execution of {@link ContractorsAccess#getAllContractors()}.
     * @throws Exception
     *             Thrown if any other error occurs.
     * 
     */
    public void getAllContractors() throws ReadAllContractorsException, Exception {

	Contractor[] contractors = this.contractorsAccess.getAllContractors();

	Log.logInfo("ClientApplicationFrameController successfuly called this.contractorsAccess.getAllContractors().");

	this.contractorsView = ContractorsViewFactory.createContractorsView(this.viewControl, contractors);
    }

    /**
     * Retrieves contractors from the database that match the provided name and/or location using
     * {@link ContractorsAccess} reference then creates a new contractors view model by calling the
     * {@link ContractorsViewFactory#createContractorsView(Object, Contractor[])} method. The contractors view reference
     * is saved in the class.
     * 
     * @param name
     *            The name of the contractor.
     * @param location
     *            The location of the contractor.
     * @throws ContractorFindException
     *             Thrown if an error occurs during the execution of {@link ContractorsAccess#findByCriteria(String[])}.
     * @throws Exception
     *             Thrown if any other error occurs.
     * 
     */
    public void searchNameLocationContractors(String name, String location) throws ContractorFindException, Exception {

	String[] criteria = new String[] { null, null, null, null, null, null };

	if (name != null && !name.isEmpty()) {

	    criteria[Data.FIELD_INDEX_NAME] = name;
	}

	if (location != null && !location.isEmpty()) {

	    criteria[Data.FIELD_INDEX_LOCATION] = location;
	}

	// try to get the contractors first from the server or local file.
	Contractor[] contractors = this.contractorsAccess.findByCriteria(criteria);

	Log.logInfo("ClientApplicationFrameController successfuly called searchNameLocationContractors.");

	this.contractorsView = ContractorsViewFactory.createContractorsView(this.viewControl, contractors);
    }

    /**
     * Shows the {@link BookingDialog} and updates the selected contractor in the table view. This function is called
     * when the user clicks the book button in the applications toolbar.
     * 
     * <p>
     * <ul>
     * <li>If no contractor is selected in the table view then this message {@link Strings#MSG_NO_CONTRACTOR_SELECTED}
     * is shown to the user and the function returns.</li>
     * <li>If the selected contractor is booked to a customer and the application runs in stand alone mode then this
     * message {@link Strings#MSG_CONTRACTOR_ALREADY_BOOKED} is shown and the function returns. In network mode this
     * function always shows the booking dialog.</li>
     * <li>The function shows the {@link BookingDialog} modal to the client frame, if the user cancel this dialog then
     * the function returns.</li>
     * <li>If the user enters a valid customer ID value in the booking dialog and hits the OK button then the booking
     * dialog is closed and the selected contractor is updated with the entered customer ID value.</li>
     * </ul>
     * 
     * @param clientWindow
     *            Reference to the client application frame.
     * @throws Exception
     *             This function does not catch any exceptions it simply forwards all exceptions to be handled by it
     *             caller.
     * 
     */
    public void bookContractor(Object clientWindow) throws Exception {

	// get the selected contractor from the view.
	Contractor contractor = this.contractorsView.getSelectedContractor();

	if (contractor == null) {
	    // if no contractor is selected then inform the user and exit.
	    DesktopGui.showInfoMessage(Strings.CAP_INFORMATION, Strings.MSG_NO_CONTRACTOR_SELECTED);
	    return;
	}

	if (this.clientApplication.getApplicationMode() == ApplicationMode.STANDALONE_CLIENT) {
	    if (contractor.isBooked()) {
		/*
		 * In client mode inform the user that it is not allowed to book an already booked contractor.
		 */
		DesktopGui.showInfoMessage(Strings.CAP_INFORMATION, Strings.MSG_CONTRACTOR_ALREADY_BOOKED);
		return;
	    }
	}

	// ask the user to enter the customer ID for this contractor.
	BookingDialog ownerDialog = new BookingDialog((JFrame) clientWindow);
	ownerDialog.setVisible(true);

	// check if the user canceled the dialog.
	if (ownerDialog.isDialogCanceled()) {
	    return;
	}

	// Create a new contractor object with the new owner ID.
	Contractor bookedContractor = contractor.copyWithOwner(ownerDialog.getOwnerID());

	// Update the contractor.
	this.contractorsAccess.updateContractor(bookedContractor);

	// update the contractor object of the UI
	contractor.setOwner(ownerDialog.getOwnerID());

	// force the selected contractor to refresh itself in the view.
	this.contractorsView.refreshSelectedContractor();
    }

    /**
     * Clears the customer ID field of the selected contractor. This function is called when the remove booking button
     * in clicked in the application's toolbar. If no contractor is selected in the table view then this message
     * {@link Strings#MSG_NO_CONTRACTOR_SELECTED} is shown and the function returns. The function calls the
     * {@link ContractorsAccess#updateContractor(Contractor)} to update the contractor in the database. The function
     * does not catch any exceptions, it delegates all exceptions to its callers.
     * 
     * @throws Exception
     *             This function does not catch any exceptions it simply forwards all exceptions to be handled by it
     *             caller.
     */
    public void unbookContractor() throws Exception {

	Contractor contractor = this.contractorsView.getSelectedContractor();

	if (contractor == null) {
	    DesktopGui.showInfoMessage(Strings.CAP_INFORMATION, Strings.MSG_NO_CONTRACTOR_SELECTED);
	    return;
	}

	// book the contractor
	Contractor unbookedContractor = contractor.copyWithEmptyOwner();

	this.contractorsAccess.updateContractor(unbookedContractor);

	// update the contractor object of the UI
	contractor.removeOwner();

	// force the selected contractor to refresh itself in the view.
	this.contractorsView.refreshSelectedContractor();
    }

    /**
     * Simply calls {@link ClientApplication#closeDatabaseFile()}.
     * 
     * @throws CloseContractorsException
     *             See {@link ClientApplication#closeDatabaseFile()}
     * @throws Exception
     *             See {@link ClientApplication#closeDatabaseFile()}
     * 
     */
    public void closeDatabaseFile() throws CloseContractorsException, Exception {

	this.clientApplication.closeDatabaseFile();
    }

    /**
     * Terminates the application by calling {@link ClientApplication#terminateApplication()}
     */
    public void terminateApplication() {

	this.clientApplication.terminateApplication();
    }
}
